<?php
declare(strict_types=1);
namespace qhweb\phpsm\ecc\sm2;

use qhweb\phpsm\ecc\Hex2ByteBuf;
use qhweb\phpsm\ecc\sm3\SM3Digest;

/**
 * SM2 与 SM3 联合签名验证及消息摘要工具类
 *
 * 本类实现了基于 SM2 公钥的数字签名验证流程，其中签名验证过程中使用了 SM3 作为哈希函数。
 * 主要功能包括：
 * - 计算 Z 值（SM2 标准中规定的用户身份与公钥等相关信息的哈希）
 * - 计算消息的 SM3 哈希值 E
 * - 组合 Z 与消息哈希得到最终用于验证的哈希输入
 * - 实现 SM2 签名验证逻辑（验证 r, s 是否匹配公钥与哈希）
 * - 提供软验证接口 YtVerfiyBySoft 以及直接获取 sm3(z+msg) 的接口
 *
 * 注意：
 * - 本类依赖外部类 SM3Digest（来自 qhweb\phpsm\ecc\sm3）与 Hex2ByteBuf（用于字节数组与十六进制互转）。
 * - 所有方法均为私有或公有接口，供外部调用以完成签名验证或哈希计算。
 * - 代码中使用了大量的字节数组操作与 GMP 大整数运算，适用于密码学安全场景。
 */
class Sm2WithSm3
{
    /**
     * 计算 SM2 标准中的 Z 值
     *
     * Z 值为 userId、曲线参数（a, b）、基点（Gx, Gy）以及用户公钥（PubKeyX, PubKeyY）的 SM3 哈希。
     * 符合 GM/T 0009-2012《SM2 密码算法使用规范》。
     *
     * @param string $userId          用户唯一标识（如 "1234567812345678"）
     * @param string $HexPubKeyX      公钥 X 分量（十六进制字符串）
     * @param string $HexPubKeyY      公钥 Y 分量（十六进制字符串）
     * @param object $generator       椭圆曲线生成器对象（需提供曲线参数与基点访问方法）
     * @return array                  Z 值的原始字节结果（通常为 32 字节，由 SM3Digest::DoFinal 输出）
     */
    private function GetZ($userId, $HexPubKeyX,$HexPubKeyY,$generator)
    {
        $md = array();
        $sm3 = new SM3Digest();

        // 1. 处理 userId 长度（大端，2 字节）
        $id=unpack("C*",$userId);// 将字符串拆分为字节序列
        $len = sizeof($id)*8;// 总比特长度
        $sm3->Update( ($len >> 8 & 0x00ff));// 高字节
        $sm3->Update( ($len & 0x00ff));// 低字节

        // 2. 处理 userId 字节内容（从第1个字节开始，共 sizeof($id) 字节）
        $sm3->BlockUpdate($id, 1, sizeof($id));

        // 3. 处理曲线参数 a（大端字节数组）
        $gmp_a = $generator->getCurve()->GetA();
        $a=Hex2ByteBuf::ConvertGmp2ByteArray($gmp_a);
        $sm3->BlockUpdate($a, 0, sizeof($a));
        // 4. 处理曲线参数 b
        $gmp_b = $generator->getCurve()->GetB();
        $b=Hex2ByteBuf::ConvertGmp2ByteArray($gmp_b);
        $sm3->BlockUpdate($b, 0, sizeof($b));
        // 5. 处理基点 G 的 x 坐标
        $gmp_gx = $generator->GetX();
        $gx=Hex2ByteBuf::ConvertGmp2ByteArray($gmp_gx);
        $sm3->BlockUpdate($gx, 0, sizeof($gx));
        // 6. 处理基点 G 的 y 坐标
        $gmp_gy = $generator->GetY();
        $gy=Hex2ByteBuf::ConvertGmp2ByteArray($gmp_gy);
        $sm3->BlockUpdate($gy, 0, sizeof($gy));
        // 7. 处理用户公钥 X 分量
        $bPubKeyX=array();
        $bPubKeyX=Hex2ByteBuf::HexStringToByteArray($HexPubKeyX);
        $sm3->BlockUpdate($bPubKeyX, 0, sizeof($bPubKeyX));
        // 8. 处理用户公钥 Y 分量
        $bPubKeyY=array();
        $bPubKeyY=Hex2ByteBuf::HexStringToByteArray($HexPubKeyY);
        $sm3->BlockUpdate($bPubKeyY, 0, sizeof($bPubKeyY));
        // 9. 完成 SM3 计算，输出 Z 值
        $sm3->DoFinal($md, 0);
        return $md;
    }
    /**
     * 计算 Z 与消息哈希值的组合哈希 E
     *
     * 用于签名验证时，将 Z 与消息哈希拼接后再次做 SM3，得到最终用于验证的输入。
     *
     * @param array $z            Z 值字节数组（通常由 GetZ 输出）
     * @param array $HashMsgValue 消息的哈希值字节数组（通常为 32 字节）
     * @return array              拼接后的哈希结果 E
     */
    private function GetE($z, $HashMsgValue)
    {
        $md = array();
        $sm3 = new SM3Digest();

        $sm3->BlockUpdate($z, 0, sizeof($z));
        // byte[] p = Encoding.Default.GetBytes(msg);
        // sm3.BlockUpdate(p, 0, p.Length);

        $sm3->BlockUpdate($HashMsgValue, 0, 32);// 假设消息哈希为 32 字节

        $sm3->DoFinal($md, 0);
        return $md;

    }

    /**
     * 计算消息的 SM3 哈希值（字节数组形式）
     *
     * @param string $msg 原始消息字符串
     * @return array      消息的 SM3 哈希值（字节数组）
     */
    public function GetMsgHash( $msg)
    {
        $md = array();
        $sm3 = new SM3Digest();
        $msgArray=unpack("C*",$msg);// 字符串转字节序列
        $sm3->BlockUpdate($msgArray, 1, sizeof($msgArray));
        $sm3->DoFinal($md, 0);
        return $md;
    }

    /**
     * SM2 签名验证核心逻辑（内部方法）
     *
     * 验证签名中的 r, s 是否与公钥、哈希值匹配，返回计算得到的 R' 值（用于与 r 比较）。
     *
     * @param array $md               组合哈希 E 的字节数组
     * @param string $PubKeyX         公钥 X（十六进制）
     * @param string $PubKeyY         公钥 Y（十六进制）
     * @param string $VerfiySign      签名值（十六进制，通常为 128 字节，r+s 拼接）
     * @param object $generator       椭圆曲线生成器
     * @return \GMP|null              计算得到的 R'（与 r 比较），验证失败返回 null
     */
    private function Sm2Verify($md, $PubKeyX, $PubKeyY, $VerfiySign,$generator)
    {
        $InSignBuf = array();

        $InSignBuf=Hex2ByteBuf::HexStringToByteArray($VerfiySign);

        $Kx = gmp_init($PubKeyX, 16);
        $Ky = gmp_init($PubKeyY, 16);

        $PubKey = $generator->getPublicKeyFrom($Kx,$Ky,null);
        $r = gmp_init(substr($VerfiySign,0, 64), 16);
        $s = gmp_init(substr($VerfiySign,64, 64), 16);

        $ecc_point_g=$generator->getCurve()->getPoint($generator->GetX(), $generator->GetY());

        $Sm2Ret=$this->sub_Sm2Verify($md, $PubKey , $r, $s,$ecc_point_g,$generator);

        if (gmp_cmp($r, $Sm2Ret) == 0) {
            return true;
        } else {
            return false;
        }
    }
    /**
     * SM2 验证子流程（内部方法）
     *
     * 实现 SM2 签名验证的数学流程：计算 t, x1y1, 并返回 (e + x1) mod n
     *
     * @param array $md               组合哈希 E
     * @param object $userKey         用户公钥对象
     * @param \GMP $r                 签名 r
     * @param \GMP $s                 签名 s
     * @param object $ecc_point_g     基点 G 的椭圆曲线点对象
     * @param object $generator       椭圆曲线生成器
     * @return \GMP|null              计算结果 R'，若 t 为 0 则返回 null
     */
    private  function  sub_Sm2Verify($md, $userKey,$r, $s,$ecc_point_g,$generator)
    {

        $generator = $userKey->getGenerator();
        $ecc_n = $generator->getOrder();

        // e_
        $md_gmp=Hex2ByteBuf::ByteArrayToHexString($md,sizeof($md));
        $e =gmp_init($md_gmp,16);
        // t
        $t=gmp_add($r,$s);
        $t=gmp_mod($t,$ecc_n);

        $zero = gmp_init(0, 10);

        if (gmp_cmp($t,$zero)==0)
            return null;


        // x1y1
        $x1y1 = $ecc_point_g->mul($s);
        $x1y1 = $x1y1->add($userKey->getPoint()->mul($t));

        // R
        return gmp_mod(gmp_add($e,$x1y1->GetX()),$ecc_n);
    }

    /**
     * 软验证接口：对外提供的 SM2 签名验证方法
     *
     * 综合调用 GetZ、GetMsgHash、GetE 与 Sm2Verify，完成完整的签名验证流程。
     *
     * @param string $id            用户 ID
     * @param string $msg           原始消息
     * @param string $PubKeyX       公钥 X（十六进制）
     * @param string $PubKeyY       公钥 Y（十六进制）
     * @param string $VerfiySign    签名值（十六进制，r+s 拼接）
     * @param object $generator     椭圆曲线生成器
     * @return bool                 签名是否有效
     */

    public function YtVerfiyBySoft($id, $msg, $PubKeyX, $PubKeyY, $VerfiySign,$generator)
    {

        $Z = array();
        $E = array();
        $MsgHashValue = array();

        $Z = $this->GetZ($id, $PubKeyX, $PubKeyY,$generator);
        $MsgHashValue = $this->GetMsgHash($msg);
        $E = $this->GetE($Z, $MsgHashValue);



        $IsVailSign = $this->Sm2Verify($E, $PubKeyX, $PubKeyY, $VerfiySign,$generator);
        return $IsVailSign;

    }
    /**
     * 直接计算 sm3(z + msg) 的哈希值（字节数组）
     *
     * 用于需要直接获得 z 与消息拼接后的 SM3 哈希结果的场景。
     *
     * @param string $msg           原始消息
     * @param string $PubKeyX       公钥 X（十六进制）
     * @param string $PubKeyY       公钥 Y（十六进制）
     * @param object $generator     椭圆曲线生成器
     * @param string $userId        用户 ID（默认 "1234567812345678"）
     * @return array                sm3(z + msg) 的哈希结果（字节数组）
     */
    public function getSm3Hash($msg,$PubKeyX, $PubKeyY, $generator, $userId="1234567812345678") {
        // sm3(z+msg)
        $z = $this->GetZ($userId, $PubKeyX, $PubKeyY,$generator);

        $md = array();
        $sm3 = new SM3Digest();
        $sm3->BlockUpdate($z, 0, sizeof($z));
        $msgArray=unpack("C*",$msg);
        $sm3->BlockUpdate($msgArray, 1, sizeof($msgArray));
        $sm3->DoFinal($md, 0);
        return $md;
    }

}